package main;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Vector;

/**
 * Modelliert eine Apache Derby Datenbank und speichert in
 * dieser die erzielten Punktzahlen der Spieler. Bei Apache
 * Derby handelt es sich um eine direkt im JDK enthaltene
 * Datenbank, muss jedoch aus Portabilitaetsgruenden extern
 * eingebunden werden. Zum Kompilieren muss das Java Archiv
 * <b>derby.jar</b> vorhanden sein.<br/>
 * <b>Wichtig:</b> Sollte derby.jar fehlen, werden Exceptions
 * geworfen, sobald versucht wird zum ersten mal etwas in die
 * Datenbank zu schreiben.<br/>
 * <b>Create Statement:</b>
 * <pre>CREATE TABLE highscore(
 * 	id INT NOT NULL GENERATED BY DEFAULT AS IDENTITY,
 * 	name VARCHAR(32),
 * 	score BIGINT,
 * 	PRIMARY KEY(id)
 * );</pre>
 * @author Chris
 * @category Speicherkomponente
 */
public class Highscore
{
	private static final String ID = "id";
	private static final String NAME = "name";
	private static final String SCORE = "score";
	private static final String TABLE_NAME = "highscore";
	private static final String SCHEMA = "APP";
	private static final String CREATE_TABLE_STM = "CREATE TABLE " + TABLE_NAME + "("
			+ ID + " INT NOT NULL GENERATED BY DEFAULT AS IDENTITY, "
			+ NAME + " VARCHAR(32), "
			+ SCORE + " BIGINT, "
			+ "PRIMARY Key(" + ID + "))";
	/**
	 * Sortieroptionen
	 * @author Chris
	 */
	public enum ORDER_TYPE{NORMAL, ASC, DESC};
	public static final int ORDER_NORMAL = (ORDER_TYPE.NORMAL).ordinal();
	public static final int ORDER_ASC = (ORDER_TYPE.ASC).ordinal();
	public static final int ORDER_DESC = (ORDER_TYPE.DESC).ordinal();
	
	/**
	 * POJO Klasse fuer einen Datenbankeintrag.
	 * Verwendet von der HighscoreGui, um ein
	 * Array aus Zeilenobjekten als HTML-Tabelle
	 * darzustellen.
	 * @author Chris
	 */
	public static class Row
	{
		public int id;
		public String name;
		public long score;
		
		public Row(int id, String name, long score)
		{
			this.id = id;
			this.name = name;
			this.score = score;
		}
	}
	
	private static Connection createConnection() throws ClassNotFoundException, SQLException
	{
		Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
		Connection connection = DriverManager.getConnection("jdbc:derby:./highscore; create=true");
		return connection;
	}
	
	/**
	 * Erzeugt die Highscoretabelle falls sie nicht schon vorher erzeugt wurde.
	 * Wird von jeder anderen Funktion verwendet, die mit der Datenbanktabelle
	 * arbeitet.
	 * @return true -> neu erstellt / false -> bereits vorhanden oder Fehler
	 */
	private static boolean createTable()
	{
		boolean created = true;
		try{
			Connection connection = createConnection();
			DatabaseMetaData metaData = connection.getMetaData();
			ResultSet tables = metaData.getTables(null, SCHEMA, TABLE_NAME, null);
			if(!tables.next())
			{
				try{
					Statement statement = connection.createStatement();
					statement.executeUpdate(CREATE_TABLE_STM);
					statement.close();
					//System.out.println("Tabelle wurde neu erstellt.");
				}catch(SQLException e){
					created = false;
				}
			}
			connection.close();
		}catch(ClassNotFoundException e){
			//System.out.println("Treiberproblem: " + e.getMessage());
		}catch(SQLException e){
			//System.out.println("Datenbankproblem: " + e.getMessage());
		}
		return created;
	}
	
	/**
	 * Schreibt ein neues Wertepaar in die Highscoretabelle.
	 * Falls der Name schon in der Tabelle existiert wird die Punktzahl
	 * nur ueberschrieben, falls diese groesser als die bisherige ist.
	 * @param name Spielername
	 * @param score erzielte Punktzahl
	 * @return false -> Fehler / true -> sonst
	 */
	public static boolean insertIntoHighscore(String name, long score)
	{
		createTable();
		boolean inserted = false;
		try{
			Connection connection = createConnection();
			Statement insertStm = connection.createStatement();
			Row row = alreadyContained(name);
			if(row == null)
				insertStm.executeUpdate("INSERT INTO " + TABLE_NAME + " (" + NAME + ", " + SCORE + ") VALUES('" + name + "', " + score + ")");
			else
				if(row.score < score)
					insertStm.executeUpdate("UPDATE " + TABLE_NAME + " SET " + SCORE + "=" + score + " WHERE " + ID + "=" + row.id);
			
			insertStm.close();
			inserted = true;
			connection.close();
		}catch(ClassNotFoundException e){
			//System.out.println("Treiberproblem: " + e.getMessage());
		}catch(SQLException e){
			//System.out.println("Fehler beim Einfuegen: " + e.getMessage());
		}
		return inserted;
	}
	
	private static Row alreadyContained(String name)
	{
		Row row = null;
		String query = "SELECT * FROM " + TABLE_NAME + " WHERE " + NAME + " = \'" + name + "\'";
		try{
			Connection connection = createConnection();
			Statement statement = connection.createStatement();
			ResultSet result = statement.executeQuery(query);
			while(result.next()){
				int id = result.getInt(ID);
				String na = result.getString(NAME);
				long score = result.getLong(SCORE);
				row = new Row(id, na, score);
			}
			connection.close();
		}catch(Exception e){
			// System.out.println("alreadyContained: " + e.getMessage());
		}
		return row;
	}
	
	/**
	 * <b>Achtung:</b> Loescht alle Eintraege der Datenbanktabelle
	 * und kann nicht rueckgaengig gemacht werden. Diese Funktion ist
	 * direkt von der HighscoreGui aufrufbar.
	 */
	public static void deleteEverything()
	{
		createTable();
		try{
			Connection connection = createConnection();
			Statement statement = connection.createStatement();
			statement.executeUpdate("DELETE FROM " + TABLE_NAME);
			statement.executeUpdate("ALTER TABLE " + TABLE_NAME + " ALTER COLUMN " + ID + " RESTART WITH 1");
			connection.close();
		}catch(ClassNotFoundException e){
			System.out.println("Treiberproblem: " + e.getMessage());
		}catch(SQLException e){
			System.out.println("Fehler beim Loeschen: " + e.getMessage());
		}
	}
	
	/**
	 * Es wird in Abhaengigkeit von der Sortieroption ein
	 * Abfragenausdruck aufgebaut, um entweder nach Reihenfolge
	 * der Zeilen-IDs oder nach auf- bzw. absteigender Reihenfolge
	 * der Highscorewerte zu sortieren. Geliefert werden alle
	 * Zeilen der Datenbanktabelle als geordnetes Array.
	 * @param type Wert aus enum ORDER_TYPE
	 * @return alle Zeilen der Tabelle als Array
	 */
	public static Row[] getAllEntries(int type)
	{
		createTable();
		Row[] rows = new Row[0];
		List<Row> list = new Vector<>();
		String sql = "SELECT * FROM " + TABLE_NAME;
		if(type == ORDER_ASC) sql += " ORDER BY " + SCORE + " ASC, " + ID + " ASC";
		else if(type == ORDER_DESC) sql += " ORDER BY " + SCORE + " DESC, " + ID + " ASC";
		try{
			Connection connection = createConnection();
			Statement statement = connection.createStatement();
			ResultSet result = statement.executeQuery(sql);
			while(result.next())
			{
				int id = result.getInt(ID);
				String name = result.getString(NAME);
				long score = result.getLong(SCORE);
				list.add(new Row(id, name, score));
			}
			rows = list.toArray(rows);
			connection.close();
		}catch(ClassNotFoundException e){
			System.out.println("Treiberproblem: " + e.getMessage());
		}catch(SQLException e){
			System.out.println("Fehler beim Lesen: " + e.getMessage());
		}
		return rows;
	}
}
